#Region "Using Directives"

Imports System
Imports System.Collections.Generic
Imports System.Text

#End Region
Namespace C4F.DevKit.Bluetooth

    Namespace ServiceAndDeviceDiscovery

        ''' <summary>
        ''' Represents a service on remote device.        
        ''' </summary>
        Public NotInheritable Class RemoteService
            Inherits Service

#Region "Private Fields"

            ''' <summary>
            ''' Queryset structure holds the information related 
            ''' to the service.
            ''' </summary>
            Private qset As QuerySet

            ''' <summary>
            ''' Address of device on which this service exists.
            ''' </summary>
            Private deviceAddr As BluetoothSocketAddress

            ''' <summary>
            ''' Socket used for connecting and disconnecting from the 
            ''' remote service.
            ''' </summary>
            Private clientSocket As Net.Sockets.Socket

            ''' <summary>
            ''' HybridDictionary used for maintaining the lifetime of an 
            ''' asynchronous operation.            
            ''' </summary>
            Private userStateToLifetime As New Collections.Specialized.HybridDictionary()

#End Region

#Region "Private Delegates And Events"

            ''' <summary>
            ''' Represents a method to be called when a message is to be 
            ''' dispatched to a synchronization context.
            ''' </summary>
            Private onConnectionCompletedDelegate As Threading.SendOrPostCallback

            ''' <summary>
            ''' Represents a method to be called when a message is to be 
            ''' dispatched to a synchronization context.
            ''' </summary>
            Private onDisconnectionCompletedDelegate As Threading.SendOrPostCallback

            ''' <summary>
            ''' Wraps the method which performs the actual task.
            ''' </summary>
            ''' <param name="asyncOp">Represents an asynchronous operation.
            ''' </param>
            Private Delegate Sub WorkerEventHandler(ByVal asyncOp As ComponentModel.AsyncOperation)

#End Region

#Region "Public Delegates And Events"

            ''' <summary>
            ''' Event handler for the Connected event.
            ''' </summary>
            ''' <param name="sender">The sender of the event.</param>
            ''' <param name="connectionEventArgs">EventArgs when an Connection
            ''' to remote service completes.</param>            
            Public Delegate Sub ConnectionEventHandler(ByVal sender As Object, ByVal connectionEventArgs As ConnectionEventArgs)

            ''' <summary>
            ''' This event is fired when an Connection to a remote service 
            ''' completes.
            ''' </summary>
            Public Event Connected As ConnectionEventHandler

            ''' <summary>
            ''' Event handler for the Disconnected event.
            ''' </summary>
            ''' <param name="sender">The sender of the event.</param>
            ''' <param name="disconnectionEventArgs">EventArgs when an 
            ''' Disconnection from a  remote service completes.</param>
            Public Delegate Sub DisconnectionEventHandler(ByVal sender As Object, ByVal disconnectionEventArgs As DisconnectionEventArgs)

            ''' <summary>
            ''' This event is fired when an Disconnection from a remote service 
            ''' completes.
            ''' </summary>
            Public Event Disconnected As DisconnectionEventHandler

#End Region

#Region "Public Property"

            ''' <summary>
            ''' Gets the device address.
            ''' </summary>
            Public ReadOnly Property DeviceAddress() As BluetoothSocketAddress

                Get
                    Return Me.deviceAddr
                End Get

            End Property

#End Region

#Region "Constructor"

            ''' <summary>
            ''' Initializes a remote service object.
            ''' </summary>
            ''' <param name="querySet">Structure containing service specific 
            ''' information.</param>
            ''' <param name="deviceAddress">Represents the device address.</param>
            ''' <exception cref="ArgumentNullException">Thrown when 
            ''' when a null reference (not set or empty) is passed 
            ''' to a method that does not accept it as a valid argument.
            ''' </exception>            
            Friend Sub New(ByVal querySet As QuerySet, ByVal deviceAddress As BluetoothSocketAddress)
                MyBase.New(querySet)

                ' Verify if all input parameters have been provided and are 
                ' valid.
                If querySet Is Nothing Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullQuerySet)
                End If

                If deviceAddress Is Nothing Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullDeviceAddress)
                End If

                Me.qset = querySet
                Me.deviceAddr = deviceAddress

                ' Initialize SendOrPostCallback delegates.
                onConnectionCompletedDelegate = New Threading.SendOrPostCallback(AddressOf ConnectionCompleted)
                onDisconnectionCompletedDelegate = New Threading.SendOrPostCallback(AddressOf DisconnectionCompleted)

            End Sub

#End Region

#Region "Private Functions"

#Region "ConnectAsync Helper Functions"

            ''' <summary>
            ''' This method is invoked via the AsyncOperation, so that it
            ''' executes on the correct thread.This method is called when
            ''' the connection is complete and raises the Connected event.
            ''' </summary>
            ''' <param name="operationState">Represents the ConnectionEventArgs 
            ''' object.</param>
            Private Sub ConnectionCompleted(ByVal operationState As Object)
                Dim connectionEventArgs As ConnectionEventArgs = TryCast(operationState, ConnectionEventArgs)
                RaiseEvent Connected(Me, connectionEventArgs)

                'Raise the connected event.

            End Sub

            ''' <summary>
            ''' Removes the task from the lifetime collection and packages the
            ''' result of the asynchronous operation in a ConnectionEventArgs 
            ''' object and then ends the asynchronous call.
            ''' </summary>
            ''' <param name="networkStream">Used for communicating with the service.
            ''' </param>
            ''' <param name="exception">An error that occurred during the 
            ''' asynchronous operation.</param>
            ''' <param name="cancelled">A value indicating whether the 
            ''' asynchronous operation was cancelled.</param>
            ''' <param name="asyncOp">Represents the asynchronous operation.
            ''' </param>

            Private Sub ConnectCompletionMethod(ByVal networkStream As Net.Sockets.NetworkStream, ByVal exception As Exception, ByVal cancelled As Boolean, ByVal asyncOp As ComponentModel.AsyncOperation)

                ' If the task was not previously canceled,
                ' remove the task from the lifetime collection.
                If Not cancelled Then
                    SyncLock userStateToLifetime.SyncRoot
                        userStateToLifetime.Remove(asyncOp.UserSuppliedState)
                    End SyncLock
                End If

                ' Package the results of the operation in a 
                ' ConnectionEventArgs.
                Dim connectionEventArgs As New ConnectionEventArgs(networkStream, exception, cancelled, asyncOp.UserSuppliedState)

                ' End the task. The asyncOp object is responsible 
                ' for marshaling the call.
                ' Note that after the call to OperationCompleted, 
                ' asyncOp is no longer usable, and any attempt to use it
                ' will cause an exception to be thrown.        
                asyncOp.PostOperationCompleted(onConnectionCompletedDelegate, connectionEventArgs)

            End Sub

            ''' <summary>
            ''' This method actucally performs the task of connecting to the 
            ''' service i.e. creating an endopint for the service and then 
            ''' connecting to it.
            ''' </summary>
            ''' <param name="asyncOp">Represents the asynchronous operation.
            ''' </param>            
            Private Sub ConnectToService(ByVal asyncOp As ComponentModel.AsyncOperation)
                Dim exception As Exception = Nothing
                Dim networkStream As Net.Sockets.NetworkStream = Nothing
                If Not TaskCanceled(asyncOp.UserSuppliedState) Then
                    Try
                        ' Create a bluetooth socket.
                        clientSocket = New Net.Sockets.Socket(DirectCast(32, Net.Sockets.AddressFamily), Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Ggp)

                        Me.deviceAddr.ServiceClassId = Me.Type.ServiceGuid

                        Dim endPoint As New BluetoothEndPoint(Me.deviceAddr, Me.deviceAddr.ServiceClassId)
                        clientSocket.Connect(endPoint)

                        networkStream = New Net.Sockets.NetworkStream(clientSocket, True)
                    Catch ex As Exception
                        Dim serviceEx As New ServiceConnectionException(SDPMessages.ErrorMsgServiceUnableToConnect + ex.Message)
                        exception = serviceEx
                    End Try
                End If
                Me.ConnectCompletionMethod(networkStream, exception, TaskCanceled(asyncOp.UserSuppliedState), asyncOp)
            End Sub

#End Region

#Region "DisconnectAsync Helper Functions"

            ''' <summary>
            ''' This method is invoked via the AsyncOperation, so that it 
            ''' executes on the correct thread.This method is called when 
            ''' the disconnection is complete and raises the Disconnected 
            ''' event.
            ''' </summary>
            ''' <param name="operationState">Represents the DisconnectionEventArgs 
            ''' object.</param>
            Private Sub DisconnectionCompleted(ByVal operationState As Object)
                Dim disconnectEventArgs As DisconnectionEventArgs = TryCast(operationState, DisconnectionEventArgs)
                RaiseEvent Disconnected(Me, disconnectEventArgs)
            End Sub

            ''' <summary>
            ''' Removes the task from the lifetime collection and packages the
            ''' result of the asynchronous operation in a 
            ''' DisconnectionEventArgs object and then ends the asynchronous 
            ''' call.
            ''' </summary>
            ''' <param name="exception">An error that occurred during the 
            ''' asynchronous operation.</param>
            ''' <param name="canceled">A value indicating whether the 
            ''' asynchronous operation was cancelled.</param>
            ''' <param name="asyncOp">Represents the asynchronous operation.
            ''' </param>
            Private Sub CompletionMethodForDisconnect(ByVal exception As Exception, ByVal canceled As Boolean, ByVal asyncOp As ComponentModel.AsyncOperation)

                ' If the task was not previously canceled,
                ' remove the task from the lifetime collection.
                If Not canceled Then
                    SyncLock userStateToLifetime.SyncRoot
                        userStateToLifetime.Remove(asyncOp.UserSuppliedState)
                    End SyncLock
                End If

                ' Package the results of the operation in a 
                ' CalculatePrimeCompletedEventArgs.
                Dim disconnectEventArgs As New DisconnectionEventArgs(exception, canceled, asyncOp)

                ' End the task. The asyncOp object is responsible 
                ' for marshaling the call.
                ' Note that after the call to OperationCompleted, 
                ' asyncOp is no longer usable, and any attempt to use it
                ' will cause an exception to be thrown. 

                asyncOp.PostOperationCompleted(onDisconnectionCompletedDelegate, disconnectEventArgs)

            End Sub

            ''' <summary>
            ''' Performs the task of disconnecting from the service.
            ''' </summary>
            ''' <param name="asyncOp">Represents the asynchronous operation.
            ''' </param>
            Private Sub DisconnectFromService(ByVal asyncOp As ComponentModel.AsyncOperation)
                Dim exception As Exception = Nothing

                If Not TaskCanceled(asyncOp.UserSuppliedState) Then
                    Try
                        clientSocket.Disconnect(False)
                    Catch ex As Exception
                        Dim serviceEx As New ServiceConnectionException(SDPMessages.ErrorMsgServiceUnableToDisconnect + ex.Message)
                        exception = serviceEx

                    End Try
                End If
                Me.CompletionMethodForDisconnect(exception, TaskCanceled(asyncOp.UserSuppliedState), asyncOp)
            End Sub

#End Region

#Region "Common Helper Function"

            ''' <summary>
            ''' This method verifies that the specified asynchrnous operation 
            ''' identified by the taskId is present in the lifetime collection.
            ''' </summary>
            ''' <param name="taskId">Identifies the asynchronous operation in
            ''' the lifetime collection.</param>
            ''' <returns>Returns true if task is cancelled else returns false.
            ''' </returns>

            Private Function TaskCanceled(ByVal taskId As Object) As Boolean
                Return (userStateToLifetime(taskId) Is Nothing)
            End Function

#End Region

#End Region

#Region "Public Functions"

            ''' <summary>
            ''' Synchronous method for connecting to the service.
            ''' </summary>
            ''' <returns>Returns the network stream used for communicating with 
            ''' the service.</returns>
            ''' <exception cref="ServiceConnectionException">Thrown when 
            ''' an error occurs while connecting the service.</exception>            
            Public Function Connect() As Net.Sockets.NetworkStream
                Dim networkStream As Net.Sockets.NetworkStream = Nothing
                Try
                    ' Create a bluetooth socket.
                    clientSocket = New Net.Sockets.Socket(DirectCast(32, Net.Sockets.AddressFamily), Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Ggp)

                    Me.deviceAddr.ServiceClassId = Me.Type.ServiceGuid

                    Dim endPoint As New BluetoothEndPoint(Me.deviceAddr, Me.deviceAddr.ServiceClassId)

                    clientSocket.Connect(endPoint)

                    ' The network stream will own the socket so that it will 
                    ' clean up nicely.

                    networkStream = New Net.Sockets.NetworkStream(clientSocket, True)
                    RaiseEvent Connected(Me, New ConnectionEventArgs(networkStream, Nothing, False, Nothing))

                    Return networkStream
                Catch ex As Exception

                    Dim serviceEx As New ServiceConnectionException(SDPMessages.ErrorMsgServiceUnableToConnect + ex.Message)
                    RaiseEvent Connected(Me, New ConnectionEventArgs(Nothing, serviceEx, False, Nothing))

                    Throw serviceEx
                End Try
            End Function

            ''' <summary>
            ''' Asynchronous method for connecting to the service.
            ''' </summary>
            ''' <param name="taskId">Identifies the asynchronous operation in
            ''' the lifetime collection.</param>
            ''' <exception cref="ServiceConnectionException">Thrown when 
            ''' an error occurs while connecting to the service.</exception>
            ''' <exception cref="ArgumentNullException">Thrown when 
            ''' when a null reference (not set or empty) is passed 
            ''' to a method that does not accept it as a valid argument.
            ''' </exception> 
            ''' <exception cref="ArgumentException">Thrown 
            ''' when one of the arguments provided to a method is not valid.
            ''' </exception> 
            Public Sub ConnectAsync(ByVal taskId As Object)
                If taskId Is Nothing Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgTaskId)
                End If
                Try
                    ' Create an AsyncOperation for taskId.
                    Dim asyncOp As ComponentModel.AsyncOperation = ComponentModel.AsyncOperationManager.CreateOperation(taskId)

                    ' Multiple threads will access the task dictionary,
                    ' so it must be locked to serialize access.
                    SyncLock userStateToLifetime.SyncRoot
                        If userStateToLifetime.Contains(taskId) Then
                            Throw New ArgumentException(SDPMessages.ErrorMsgTaskIdUnique, SDPMessages.ErrorMsgTaskId)
                        End If

                        userStateToLifetime(taskId) = taskId
                    End SyncLock

                    ' Start the asynchronous operation.
                    Dim workerDelegate As New WorkerEventHandler(AddressOf ConnectToService)
                    workerDelegate.BeginInvoke(asyncOp, Nothing, Nothing)
                Catch ex As Exception
                    Dim serviceEx As New ServiceConnectionException(SDPMessages.ErrorMsgServiceUnableToConnect + ex.Message)
                    Throw serviceEx
                End Try
            End Sub

            ''' <summary>
            ''' Synchronous method for disconnecting.
            ''' </summary>         
            ''' <exception cref="ServiceConnectionException">Thrown when 
            ''' an error occurs while disconnecting from the service.</exception>            
            Public Sub Disconnect()
                Try
                    clientSocket.Disconnect(False)

                    RaiseEvent Disconnected(Me, New DisconnectionEventArgs(Nothing, False, Nothing))
                Catch ex As Exception
                    Dim serviceEx As New ServiceConnectionException(SDPMessages.ErrorMsgServiceUnableToDisconnect + ex.Message)
                    RaiseEvent Disconnected(Me, New DisconnectionEventArgs(serviceEx, False, Nothing))


                    Throw serviceEx
                End Try
            End Sub

            ''' <summary>
            ''' Asynchronous method for disconnecting from the service.
            ''' </summary>
            ''' <param name="taskId">Identifies the asynchronous operation in
            ''' the lifetime collection.</param>
            ''' <exception cref="ServiceConnectionException">Thrown when 
            ''' an error occurs while disconnecting from the service.</exception>
            ''' <exception cref="ArgumentException">Thrown 
            ''' when one of the arguments provided to a method is not valid.
            ''' </exception> 
            Public Sub DisconnectAsync(ByVal taskId As Object)
                ' Create an AsyncOperation for taskId.
                Dim asyncOp As ComponentModel.AsyncOperation = ComponentModel.AsyncOperationManager.CreateOperation(taskId)

                ' Multiple threads will access the task dictionary,
                ' so it must be locked to serialize access.
                SyncLock userStateToLifetime.SyncRoot
                    If userStateToLifetime.Contains(taskId) Then
                        Throw New ArgumentException(SDPMessages.ErrorMsgTaskIdUnique, SDPMessages.ErrorMsgTaskId)
                    End If

                    userStateToLifetime(taskId) = asyncOp.UserSuppliedState
                End SyncLock

                ' Start the asynchronous operation.
                Try
                    Dim workerDelegate As New WorkerEventHandler(AddressOf DisconnectFromService)
                    workerDelegate.BeginInvoke(asyncOp, Nothing, Nothing)
                Catch ex As Exception
                    Dim serviceEx As New ServiceConnectionException(SDPMessages.ErrorMsgServiceUnableToDisconnect + ex.Message)
                    Throw serviceEx
                End Try

            End Sub

            ''' <summary>
            ''' Cancels the asynchronous operation.
            ''' </summary>
            ''' <param name="taskId">Identifies the asynchronous operation in
            ''' the lifetime collection.</param>
            ''' <exception cref="ComponentModel.InvalidAsynchronousStateException">Thrown when 
            ''' an error occurs while cancelling a non-existent operation.</exception>
            ''' <exception cref="ArgumentNullException">Thrown when 
            '''  a null reference (not set or empty) is passed 
            ''' to a method that does not accept it as a valid argument.
            ''' </exception> 
            Public Sub CancelAsync(ByVal taskId As Object)
                If taskId Is Nothing Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgTaskId)
                End If

                Dim asyncOp As ComponentModel.AsyncOperation = TryCast(userStateToLifetime(taskId), ComponentModel.AsyncOperation)

                If asyncOp IsNot Nothing Then
                    SyncLock userStateToLifetime.SyncRoot
                        userStateToLifetime.Remove(taskId)
                    End SyncLock
                Else
                    Throw New ComponentModel.InvalidAsynchronousStateException(SDPMessages.ErrorMsgInvalidTaskId)
                End If

            End Sub

            ''' <summary>
            ''' Verifies if the service is connected.
            ''' </summary>
            ''' <returns>Returns true if connected else returns false.</returns>
            Public Function IsConnected() As Boolean
                If clientSocket Is Nothing Then
                    Return False
                Else
                    Return clientSocket.Connected
                End If
            End Function

#End Region

        End Class

    End Namespace

End Namespace
